﻿using System;
using System.Configuration;
using System.Net;
using System.Net.Sockets;
using System.Text;
using IrcBot.IrcSender;

namespace IrcBot.Listener
{
    internal class SocketListener
    {
        private class SocketState
        {
            public Socket WorkSocket;
            public const int BufferSize = 1024;
            public readonly byte[] Buffer = new byte[BufferSize];
        }

        #region Private fields

        private const int DefaultListenPort = 8081;
        private readonly Socket _listeningSocket;

        #endregion

        #region Listener thread (spawned by main thread)

        internal SocketListener()
        {
            var ipAddress = GetLocalIpAddress();
            var port = GetListenPort();
            var endpoint = new IPEndPoint(ipAddress, port);
            _listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _listeningSocket.Bind(endpoint);
            _listeningSocket.Listen(20);
            _listeningSocket.BeginAccept(new AsyncCallback(AcceptConnection), _listeningSocket);

            Console.WriteLine(string.Format("Listening for connections on {0}:{1}. To exit type 'quit'.", ipAddress, port));
        }


        #region Listener configuration methods

        private static int GetListenPort()
        {
            var listenPort = 0;
            try
            {
                var listenPortAsString = ConfigurationManager.AppSettings["TcpListenPort"];
                if (!string.IsNullOrEmpty(listenPortAsString))
                {
                    if (int.TryParse(listenPortAsString, out listenPort))
                    {
                        return listenPort;
                    }
                }

                return DefaultListenPort;
            }
            finally
            {
                if (listenPort == 0)
                {
                    Console.WriteLine(string.Format("Unable to read listening port configuration (setting TcpListenPort in appSettings). Listening on default port [{0}].", DefaultListenPort));
                }
            }
        }

        private static IPAddress GetLocalIpAddress()
        {
            foreach (var ip in Dns.GetHostAddresses(Dns.GetHostName()))
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                    return ip;
            }

            throw new IpAddressResolutionException("Could not determine network IP of local machine.");
        }

        #endregion

        #endregion

        #region Clean up methods

        internal void Dispose()
        {
            Dispose(true);
        }

        private void Dispose(bool suppressFinalise)
        {
            BotController.DisposeInstance();

            if (_listeningSocket != null && _listeningSocket.Connected)
            {
                _listeningSocket.Close();
            }

            if (suppressFinalise)
            {
                GC.SuppressFinalize(this);
            }
        }

        ~SocketListener()
        {
            Dispose(false);
        }

        #endregion

        #region Client connection thread (spawned by Listener thread)

        private void AcceptConnection(IAsyncResult ar)
        {
            var listenSocket = (Socket) ar.AsyncState;
            var receiveSocket = listenSocket.EndAccept(ar);

            var state = new SocketState { WorkSocket = receiveSocket };
            receiveSocket.BeginReceive(state.Buffer, 0, SocketState.BufferSize, 0, new AsyncCallback(ReadCallback), state);

            listenSocket.BeginAccept(AcceptConnection, _listeningSocket);
        }

        private static void ReadCallback(IAsyncResult ar)
        {
            var stateObject = (SocketState) ar.AsyncState;

            var receivedMessage = Encoding.Default.GetString(stateObject.Buffer).TrimEnd('\0');
            stateObject.WorkSocket.Disconnect(false);
            stateObject.WorkSocket.Close();

            BotController.GetInstance().SendMessage(new IrcMessage(receivedMessage));
        }

        #endregion


        public event MessageEventHandler OnStartListening;
        public event MessageEventHandler OnStopListening;

    }
}
